import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { ChangeDetectionStrategy, Component, Injectable, Input, ViewChild } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { CommonModule } from '@angular/common';
import { of, Subject } from 'rxjs';
import { filter as _filter } from 'lodash';
import { delay, startWith } from 'rxjs/operators';
import { PaginatorModule } from '../pagination/paginator.module';
import { DatatableModule } from './datatable.module';
import { FilterItem } from './interface';
import { DatatableComponent } from './datatable.component';

// 模拟异步数据返回
@Injectable()
export class UserService {
  length = 100;
  users;

  constructor() {
    this.users = this.initUser();
  }

  getUsers(data: { [key: string]: any }) {
    let { users } = this;
    const { pageIndex } = data;
    const { pageSize } = data;
    const { filter } = data;
    const startIndex = pageIndex * pageSize;
    const endIndex = (pageIndex + 1) * pageSize;

    if (filter) {
      users = _filter(users, (item) => filter.value[0] == item.age);
    }
    users = users.slice(startIndex, endIndex);

    return of({ users }).pipe(delay(300));
  }

  initUser() {
    const users = Array.from({ length: this.length }).map((_, key) => ({
      name: `user${key}`,
      age: 100 + key,
    }));
    return users;
  }
}

@Component({
  template: `
    <lv-datatable
      [lvData]="noResult ? [] : lvData"
      #lvTable
      [lvAsync]="lvAsync"
      [lvSize]="lvSize"
      [lvFake]="lvFake"
      [lvScroll]="lvScroll"
      [lvPaginator]="page"
      [lvVirtualScroll]="lvVirtualScroll"
      [lvVirtualItemSize]="lvVirtualItemSize"
      [lvVirtualMaxBuffer]="lvVirtualMaxBuffer"
      [lvVirtualMinBuffer]="lvVirtualMinBuffer"
    >
      <thead>
        <tr>
          <th [lvLeft]="lvLeft">Name</th>
          <th
            [lvRight]="lvRight"
            [lvShowFilter]="lvShowFilter"
            [(lvFilters)]="ageFilters"
            (lvFilterChange)="filterChange($event)"
            [lvFilterMultiple]="false"
            lvCellKey="age"
            [lvFilterAuto]="true"
          >
            Age
          </th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor="let item of lvTable.renderData">
          <td [lvLeft]="lvLeft">{{ item.name }}</td>
          <td [lvRight]="lvRight">{{ item.age }}</td>
        </tr>
      </tbody>
    </lv-datatable>
    <lv-paginator #page [lvPageSize]="lvPageSize" [lvPageSizeOptions]="lvPageSizeOptions"></lv-paginator>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
class TestDatatableComponent {
  @Input() noResult = false;
  @Input() lvData = [
    { name: 'user1', age: 10 },
    { name: 'user2', age: 12 },
    { name: 'user3', age: 11 },
  ];

  @Input() lvSize = 'default';
  @Input() lvFake = false;
  @Input() lvLeft = 'left';
  @Input() lvRight = 'left';
  @Input() lvScroll = { x: null, y: null, autosize: false };
  @Input() lvVirtualScroll = false;
  @Input() lvVirtualItemSize = 40;
  @Input() lvVirtualMaxBuffer = 200;
  @Input() lvVirtualMinBuffer = 100;
  @Input() lvAsync = false;

  @Input() lvPageSize = 2;
  @Input() lvPageSizeOptions = [1, 2, 3];
  @Input() lvShowFilter = false;

  @ViewChild(DatatableComponent, { static: false }) lvTable: DatatableComponent;
  constructor(private service: UserService) {}

  pageIndex = 0;
  params: { [key: string]: any } = { pageIndex: this.pageIndex, pageSize: this.lvPageSize };
  params$ = new Subject<{ [key: string]: any }>();
  filterState: FilterItem[] = [];

  ageFilters = [
    {
      key: 0,
      label: '10',
      value: '10',
    },
    {
      key: 1,
      label: '11',
      value: '11',
    },
    {
      key: 2,
      label: '100',
      value: '100',
    },
  ];

  filterChange(options: any) {
    this.updateParams({ filter: options });
    this.lvTable.filter(options);
  }

  updateParams(data) {
    this.params$.next((this.params = { ...this.params, ...data }));
  }

  initAsyncData() {
    this.params$.pipe(startWith(this.params)).subscribe((res) => {
      this.service.getUsers(res).subscribe((res: { users: []; total: number }) => {
        this.lvData = res.users;
      });
    });
  }
}

describe('TableComponent', () => {
  beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      declarations: [TestDatatableComponent],
      imports: [DatatableModule, CommonModule, PaginatorModule, NoopAnimationsModule],
      providers: [UserService],
      teardown: { destroyAfterEach: true },
    }).compileComponents();
  }));

  describe('render', () => {
    let component: TestDatatableComponent;
    let fixture: ComponentFixture<TestDatatableComponent>;
    const renderComp = fakeAsync((para) => {
      for (const [key, value] of Object.entries(para)) {
        component[key] = value;
      }
      fixture.detectChanges();
      tick(500);
      fixture.detectChanges();
      expect(fixture).toMatchSnapshot();
      tick(1500);
    });

    beforeEach(fakeAsync(() => {
      fixture = TestBed.createComponent(TestDatatableComponent);
      component = fixture.componentInstance;
      fixture.detectChanges();
      tick(500);
    }));

    // 不同尺寸渲染
    it('with small lvSize', () => {
      renderComp({ lvSize: 'small' });
    });

    it('with large lvSize', () => {
      renderComp({ lvSize: 'large' });
    });

    // 固定列渲染
    it('with lvLeft and lvRight', () => {
      renderComp({ lvLeft: '0px', lvRight: '0px' });
    });

    // 横向和纵向滚动渲染
    it('with lvScroll', () => {
      renderComp({ lvScroll: { x: '100px', y: '200px' } });
    });

    // 虚拟滚动渲染（lvVirtualItemSize lvVirtualMaxBuffer lvVirtualMinBuffer）
    it('with lvVirtualScroll', () => {
      renderComp({ lvVirtualScroll: true, lvVirtualItemSize: 70, lvVirtualMaxBuffer: 250, lvVirtualMinBuffer: 150 });
    });

    // 空数据和傀儡表格渲染
    it('with noResult and lvFake', () => {
      renderComp({ noResult: true });
      renderComp({ lvFake: true });
    });
  });

  describe('work', () => {
    let component: TestDatatableComponent;
    let fixture: ComponentFixture<TestDatatableComponent>;
    const contentClass = '.lv-table-td-content';

    const clickFilterItem = (index, expectContent) => {
      // 点击过滤标签，弹出过滤面板
      const filterIcon = fixture.nativeElement.querySelector('.lv-icon-host');
      filterIcon.dispatchEvent(new Event('click'));
      tick(1500);
      fixture.detectChanges();

      const filterItem = document.querySelectorAll('.lv-filter-select-item')[index];
      filterItem.dispatchEvent(new Event('click'));
      tick(500);
      fixture.detectChanges();

      const tdContent = fixture.nativeElement.querySelectorAll('.lv-table-td-content');
      expect(tdContent.length).toEqual(2);
      expect(tdContent[1].textContent).toEqual(expectContent);
      tick(500);
    };

    beforeEach(fakeAsync(() => {
      fixture = TestBed.createComponent(TestDatatableComponent);
      component = fixture.componentInstance;
      fixture.detectChanges();
      tick(500);
    }));

    // 分页器pageSize改变后，表格显示正确
    it('show correctly when pageSize changed', fakeAsync(() => {
      fixture.detectChanges();
      expect(fixture.nativeElement.querySelectorAll(contentClass).length).toBe(4);
      const select = fixture.nativeElement.querySelector('lv-select');
      select.dispatchEvent(new Event('click'));
      tick(500);

      // 选择每页显示3行
      const option = document.querySelectorAll('lv-option')[2];
      option.dispatchEvent(new Event('click'));
      tick(500);
      fixture.detectChanges();

      // 分页器pageSize改变后，查看表格当前存在3行
      expect(fixture.nativeElement.querySelectorAll(contentClass).length).toBe(6);
      tick(1500);
    }));

    // 点击分页器跳转，表格数据能正常变化
    test('show correctly when paginator jump', fakeAsync(() => {
      fixture.detectChanges();
      expect(fixture.nativeElement.querySelectorAll(contentClass).length).toBe(4);

      // 点击跳转到第2页
      const jumpButton = fixture.nativeElement.querySelectorAll('.lv-paginator-jump-button')[2];
      jumpButton.dispatchEvent(new Event('click'));
      tick(500);
      fixture.detectChanges();
      expect(fixture.nativeElement.querySelectorAll(contentClass).length).toBe(2);
    }));

    // 表格数据变化时，分页器页码正确变化
    test('pageNum changes correctly when data changed', fakeAsync(() => {
      const newData = [
        { name: 'user1', age: 10 },
        { name: 'user2', age: 12 },
        { name: 'user3', age: 11 },
        { name: 'user4', age: 13 },
        { name: 'user5', age: 14 },
      ];
      component.lvData = newData;
      fixture.detectChanges();
      tick(500);
      fixture.detectChanges();
      expect(fixture.nativeElement.querySelector('.lv-paginator-total').textContent).toContain('共 5 条');
      tick(1500);
    }));

    // 异步数据表格功能正确，lvAsync（同步和异步切换后，执行过滤操作）
    test('with lvAsync', fakeAsync(() => {
      // 初始为同步
      component.lvShowFilter = true;
      fixture.detectChanges();
      tick(500);
      fixture.detectChanges();

      // 切换到异步，异步过滤生效
      component.lvAsync = true;
      component.initAsyncData();
      fixture.detectChanges();
      tick(500);
      fixture.detectChanges();
      clickFilterItem(2, '100');

      // 再次切换到同步，同步过滤生效（不会走异步过滤）
      component.lvAsync = false;
      component.lvData = [
        { name: 'user1', age: 10 },
        { name: 'user2', age: 12 },
        { name: 'user3', age: 11 },
      ];
      fixture.detectChanges();
      tick(500);
      fixture.detectChanges();
      clickFilterItem(1, '11');
    }));

    // TODO 虚拟滚动场景测试lvVirtualScroll，lvVirtualItemSize，lvVirtualMaxBuffer，lvVirtualMinBuffer，lvVirtualTrackBy, 触发对应lvScrollEnd，lvScrollingChange（当前UT环境不支持）

    // TODO 表头拖拽改变列宽（lvResize, lvResizeMode, lvResizeMinWidth）（当前UT环境不支持）
  });
});
